using System;
using System.Collections.Generic;
using System.IO;
using BitTorrent.Client.Peer.Message.Standard;

namespace BitTorrent.Client.Peer.Message
{
    internal static class PeerMessageFactory
    {
        private static readonly Dictionary<PeerMessageId, Func<BinaryReader, int, PeerMessage>> Dictionary;

        static PeerMessageFactory() {
            Dictionary = new Dictionary<PeerMessageId, Func<BinaryReader, int, PeerMessage>>
                         {
                             {PeerMessageId.Choke, (reader, i) => new Choke()},
                             {PeerMessageId.UnChoke, (reader, i) => new UnChoke()},
                             {PeerMessageId.Intrested, (reader, i) => new Intrested()},
                             {PeerMessageId.NotIntrested, (reader, i) => new NotIntrested()},
                             {PeerMessageId.Have, (reader, i) => new Have(reader)},
                             {PeerMessageId.Bitfield, (reader, i) => new Standard.Bitfield(reader, i)},
                             {PeerMessageId.Request, (reader, i) => new Request(reader)},
                             {PeerMessageId.Piece, (reader, i) => new Piece(reader, i)},
                             {PeerMessageId.Cancel, (reader, i) => new Cancel(reader)}
                         };
        }

        public static PeerMessage GetMessage(byte[] message) {
            using (var memory = new MemoryStream(message)) {
                return GetMessage(memory);
            }
        }

        public static PeerMessage GetMessage(Stream input) {
            return GetMessage(new BinaryReader(input));
        }

        public static PeerMessage GetMessage(BinaryReader reader) {
            int length = GetInt32From(reader);
            
            // KeepAlive is a message with zero bytes
            if (length == 0) {
                return new KeepAlive();
            }

            var id = (PeerMessageId)reader.ReadByte();
            Func<BinaryReader, int, PeerMessage> creator;
            if (!Dictionary.TryGetValue(id, out creator)) {
                return new UnknownPeerMessage();
            }
            return creator(reader, length);
        }

        private static int GetInt32From(BinaryReader reader) {
            var lengthBigEndian = new byte[4];
            reader.Read(lengthBigEndian, 0, 4);

            if (BitConverter.IsLittleEndian) {
                Array.Reverse(lengthBigEndian);
            }

            return BitConverter.ToInt32(lengthBigEndian, 0);
        }
    }
}
